#pragma once
#include "halley/api/halley_api_internal.h"

#include <xaudio2.h>

namespace Halley
{
	class XAudio2AudioOutput;

	class XAudio2MasteringVoice
	{
	public:
		XAudio2MasteringVoice(XAudio2AudioOutput& audio, const AudioSpec& spec, String deviceId);
		~XAudio2MasteringVoice();

	private:
		IXAudio2MasteringVoice* masteringVoice = nullptr;
	};

	class XAudio2SourceVoice : public IXAudio2VoiceCallback
	{
	public:
		XAudio2SourceVoice(XAudio2AudioOutput& audio, const AudioSpec& spec, AudioCallback callback);
		~XAudio2SourceVoice();

		void queueAudio(gsl::span<const float> samples);
		void play();

		uint64_t getSamplesPlayed() const;

		void __stdcall OnVoiceProcessingPassStart(UINT32 BytesRequired) override;
		void __stdcall OnVoiceProcessingPassEnd() override;

		void __stdcall OnStreamEnd() override;

		void __stdcall OnBufferStart(void* pBufferContext) override;
		void __stdcall OnBufferEnd(void* pBufferContext) override;

		void __stdcall OnLoopEnd(void* pBufferContext) override;

		void __stdcall OnVoiceError(void* pBufferContext, HRESULT Error) override;

	private:
		bool running;
		XAudio2AudioOutput& audio;
		AudioCallback callback;

		IXAudio2SourceVoice* voice = nullptr;

		ConditionVariable condition;
		Mutex mutex;

		struct Buffer {
			Vector<float> buffer;
			bool busy = false;
		};
		Vector<Buffer> buffers;

		Buffer* getBuffer(size_t size);
		void returnBuffer(Buffer* buffer);
	};

	class XAudio2AudioDevice : public AudioDevice
	{
	public:
		XAudio2AudioDevice(const String& name);

		String getName() const override;

	private:
		String name;
	};

	class XAudio2AudioOutput : public AudioOutputAPIInternal
	{
	public:
		void init() override;
		void deInit() override;
		
		Vector<std::unique_ptr<const AudioDevice>> getAudioDevices() override;
		AudioSpec openAudioDevice(const AudioSpec& requestedFormat, const AudioDevice* device, AudioCallback prepareAudioCallback) override;
		void closeAudioDevice() override;

		void startPlayback() override;
		void stopPlayback() override;

		void onAudioAvailable() override;
		bool needsMoreAudio() override;

		bool needsAudioThread() const override;

		uint64_t getSamplesPlayed() const override;
		uint64_t getSamplesSubmitted() const override;
		uint64_t getSamplesLeft() const override;

		IXAudio2& getXAudio2();

	private:
		IXAudio2* xAudio2 = nullptr;
		std::unique_ptr<XAudio2MasteringVoice> masterVoice;
		std::unique_ptr<XAudio2SourceVoice> voice;
		AudioCallback callback;
		AudioSpec format;
		Vector<float> buffer;
		std::atomic_uint64_t samplesSubmitted = 0;

		uint64_t playbackPos = 0;
		std::chrono::high_resolution_clock::time_point lastSubmissionTime;
		mutable Mutex playbackMutex;

		void consumeAudio();
	};
}
